/*
 * Copyright (C) 2017 Intel Corporation.  All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */


// Copyright (c) WanSheng Intelligent Corp. All rights reserved.


#define LOG_TAG						"LOG_UTIL"


#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/statfs.h>
#include <stdbool.h>
#include <ctype.h>
#include "logs.h"
#include "iniparser.h"
#include "misc.h"
#include "agent_core_lib.h"



#ifndef MAX_LOG_CONTEXS
#define MAX_LOG_CONTEXS 10
#endif
#ifndef MAX_LOG_MB 
#define MAX_LOG_MB 10
#endif

unsigned char log_level = LOG_LEVEL_DEFAULT;
unsigned long log_tag_mask[LOG_MASK_SIZE]  = {0};

// Max log file size limited to 5M Bytes by default
static unsigned int g_log_size_max = MAX_LOG_MB;
msg_queue_t g_log_queue = NULL;

char g_log_mode[10] = "logfile" ;
// app name have no full path, like lwm2mserver
char g_log_app_name[24] = {0};

static char * g_log_dir = NULL;
static char * g_log_cfg_path = "";

static void signal_handler(int signal);
static time_t g_log_create_time = 0;
static int max_log_days = 7 ;
static int g_keep_multi_closed_log = 0;

typedef struct 
{
    FILE* log_fp;
    time_t log_create_time;
    int max_log_day;
    time_t last_size_check_time;
    long max_size_KB;
    char * log_path;
} log_context_t;

log_context_t * g_log_ctxs[MAX_LOG_CONTEXS] = {0};

typedef struct 
{
    log_id_t idx;
    char log_content[1];
} log_body_t;


static log_context_t * log_ctx(log_id_t idx)
{
    if(idx >= MAX_LOG_CONTEXS)
        return NULL;

    return g_log_ctxs[idx];
}

static FILE* get_log_fp(log_id_t idx)
{
    if(idx >= MAX_LOG_CONTEXS)
        return NULL;
    if(g_log_ctxs[idx] == NULL) 
        return NULL;
    return g_log_ctxs[idx]->log_fp;
}

void close_log_fp(log_id_t idx)
{
    if(idx >= MAX_LOG_CONTEXS)
        return; 

    if(g_log_ctxs[idx]->log_fp)
    {
        struct tm *tmp;
        time_t t = time(NULL);
        tmp = localtime(&t);
        if(tmp)
        {
            char str[16]; // 20101112-123456, 15Bytes+1 '0'
            strftime(str, sizeof(str), "%Y%m%d-%H%M%S", tmp);
            fprintf(g_log_ctxs[idx]->log_fp, "\r\nClosed time:%s\r\n", str);
        }
        fflush(g_log_ctxs[idx]->log_fp);
        fclose(g_log_ctxs[idx]->log_fp);
        g_log_ctxs[idx]->log_fp = NULL;
    }
}

void sig_term_handler(int signum, siginfo_t *info, void *ptr)
{
        WARNING("[%s] SIGTERM received, generating new log..", g_log_app_name);
        fprintf(log_get_handle(), "SIGTERM received, exiting.. [%s] - line [%d]\n", __FUNCTION__, __LINE__);
        fflush(log_get_handle());
        abort();
        exit(-1);
}

void catch_sigterm()
{
    static struct sigaction _sigact;

    memset(&_sigact, 0, sizeof(_sigact));
    _sigact.sa_sigaction = sig_term_handler;
    _sigact.sa_flags = SA_SIGINFO;

    sigaction(SIGTERM, &_sigact, NULL);
}


// the function should be called in signal_handler
// when daemon found disk quota reach, log file size
// exceed or other specific condition.

char * stime2(char *s , int len, char * time_format)
{
    time_t timep;
    struct tm *p;
    static char st[100];
    if(s == NULL){
        s = st;
        len = sizeof(st);
    }

    time(&timep);
    p=localtime(&timep);
    strftime(s, len, time_format, p);
    return s;
}

char * now_str(char *s )
{
    time_t timep;
    struct tm *p;
    static char st[100];
    if(s == NULL)
        s = st;

    time(&timep);
    p=localtime(&timep);
    sprintf(s,"%d-%d-%d_%d-%d",(1900+p->tm_year),(1+p->tm_mon),p->tm_mday,p->tm_hour,p->tm_min);

    return s;

}


bool check_size_log(log_context_t * ctx)
{
    // no log file.
    if((NULL == ctx->log_fp))
        return false;

    time_t t = time(NULL);
    if((t - ctx->last_size_check_time) < 60 * 10)
        return false;

    ctx->last_size_check_time = t;

    fseek (ctx->log_fp, 0, SEEK_END);   // non-portable
    long active_log_size = ftell (ctx->log_fp);
    if(active_log_size  > ctx->max_size_KB * 1000)  // KBytes
    {
        return true;
    }

    return false;
}


static const char* log_filename(log_id_t logid)
{
    log_context_t * ctx = log_ctx(logid);
    if(ctx == NULL) return NULL;
    return (const char*)ctx->log_path;
}

//
FILE * log_get_handle()
{
    if(NULL == get_log_fp(DEFAULT_LOG_ID))
        return stderr;
    else
        return get_log_fp(DEFAULT_LOG_ID);
}

FILE*  create_log(const char * log_path)
{
    struct tm *tmp;

    FILE* local_log_fp ;

    if(log_path == NULL) return NULL;

    local_log_fp = fopen(log_path, "a+");
    if(NULL == local_log_fp)
    {
        printf("!!! Failed to create log file %s. error: %d\n", log_path, errno);
        return NULL;
    }
    else
    {
        g_log_create_time = time(NULL);
        tmp = localtime(&g_log_create_time);
        if(tmp)
        {
            char str[200];
            strftime(str, sizeof(str), "%Y%m%d-%H%M%S", tmp);
            fprintf(local_log_fp, "\nLog open time:%s.\r\n",
                    str);
        }

        fprintf(local_log_fp, "Max log life: %u days, keep multiple closed file: %d\r\n", max_log_days, g_keep_multi_closed_log);
        fprintf(local_log_fp, "Log level=%d, mask=%X. LOG_ERROR=%u\r\n",
                log_level, (unsigned int)log_tag_mask[0], LOG_ERROR);
        for(int i=1;i<LOG_MASK_SIZE;i++)
        {
            fprintf(local_log_fp, "mask-%d=0x%lX\n", i, log_tag_mask[i]);
        }
        fprintf(local_log_fp, "Log config file format:\r\n\tlog_max_mb={max log size in MB}\r\n\tmulti_closed_log={0|1}\r\n\tmax_log_days={max days of an active log}\r\n");
    }

    return local_log_fp;
}


// close the active log and move it into the closed sub folder
// and then create a new active log.
// log_refresh() is executed in the logging thread.
static void log_refresh(log_id_t logid)
{
    char cmd[512];
    log_context_t * ctx =  log_ctx(logid);
    if(ctx == NULL) return;

    if(0 != strcmp("logfile", g_log_mode))
    {
        printf("config file: log to stdout!\n");
        close_log_fp(logid);
        return;
    }

    // close log file.
    if((NULL != get_log_fp(logid)))
    {
        close_log_fp(logid);
        if(DEFAULT_LOG_ID == logid)
        {
            if(g_keep_multi_closed_log){
            snprintf(cmd, sizeof(cmd), "mv -f %s %sclosed/%s-%s.log",
                    ctx->log_path,
                    g_log_dir,
                    g_log_app_name,
                    now_str(NULL));
        }
            else{
                snprintf(cmd, sizeof(cmd), "mv -f %s %sclosed/%s.log",
                    ctx->log_path,
                    g_log_dir,
                    g_log_app_name);
            }
        }
        else
        {
            snprintf(cmd, sizeof(cmd), "mv -f %s %s.1",
                    ctx->log_path,
                    ctx->log_path);
        }

        int ret = system(cmd);
        (void) ret;
    }

    g_log_ctxs[logid]->log_fp = create_log(log_filename(logid));
    fprintf(g_log_ctxs[logid]->log_fp,"Max log size: %ld KB\r\n", g_log_ctxs[logid]->max_size_KB);

}


int q_log_quit = 0;
#define  CMD_REFRESH 101
#define  CMD_LOG     0
void *thread_logger(void *arg)
{
    THREAD_NAME("logger");
    while(q_log_quit == 0)
    {
        msg_t msg = get_msg(g_log_queue, 60);

        if (msg == NULL)
        {
            // default log max time handling
             if(g_log_create_time && (time(NULL) - g_log_create_time) > max_log_days*(24*3600))
            {
                log_refresh(DEFAULT_LOG_ID);
            }
            continue;
        }

        if(get_msg_tag(msg) == CMD_REFRESH)
            log_refresh(DEFAULT_LOG_ID);

        else if(get_msg_body(msg) && CMD_LOG == get_msg_tag(msg))
        {
            log_body_t * body = (log_body_t*) get_msg_body(msg);
            log_context_t * ctx = log_ctx(body->idx);
            if(ctx == NULL){
                free_msg(msg);
                continue;
            }
            FILE * fp = ctx->log_fp;
            if(fp == NULL) fp = stdout;
            fputs( (char *)body->log_content, fp);
            fflush(ctx->log_fp);

            if(check_size_log(ctx))
            {
                log_refresh(body->idx);
            }
        }

        free_msg(msg);
    }

    return NULL;
}





char* log_to()
{
    if(get_log_fp(DEFAULT_LOG_ID))
    {
        return "file";
    }
    else
    {
        return "stdout";
    }
}

int log_update_config()
{

    dictionary  *   ini = NULL;
    int ret = 0;
    int i;
    char str[128];

    // leave 3 mask IDs not initialized.
    for(i=0;i<LOG_MASK_SIZE-3;i++) log_tag_mask[i] = LOG_MASK_DEFAULT;

    if(g_log_cfg_path && access(g_log_cfg_path, F_OK) == 0)
        ini = iniparser_load(g_log_cfg_path);
    if(NULL == ini)
    {
        strncpy(g_log_mode, "logfile", sizeof(g_log_mode)-1);
        printf("Unable to load log config %s\n", g_log_cfg_path?g_log_cfg_path:"");
    }
    else
    {
        // create the log output stream
        const char* log = iniparser_getstring(ini, "default:out", "logfile");

        // check any overwrite config from the app config
        strcpy(str, g_log_app_name);
        strcat(str, ":out");
        log = iniparser_getstring(ini, str, log);

        // check any overwrite config from the urgent config
        log = iniparser_getstring(ini, "urgent:out", log);
        log_level	 = iniparser_getint(ini, "default:level", LOG_LEVEL_DEFAULT);
        log_tag_mask[0] = iniparser_getint(ini, "default:mask", 0xFFFF);
        for(i=1;i<LOG_MASK_SIZE;i++) log_tag_mask[i] = log_tag_mask[0];

        // initialized mask for COAP and SDK (masks:8,9)
        log_tag_mask[LOG_MASK_SIZE-1] = 0xFF;
        log_tag_mask[LOG_MASK_SIZE-2] = 0xFF;

        g_log_size_max = iniparser_getint(ini, "default:log_max_mb", MAX_LOG_MB);
        max_log_days = iniparser_getint(ini, "default:max_log_days", max_log_days);  // megabytes
        g_keep_multi_closed_log = iniparser_getint(ini, "default:multi_closed_log", 0);

        // overwrite the value if the app has its own settings
        if(g_log_app_name[0])
        {
            strcpy(str, g_log_app_name);
            strcat(str, ":level");
            log_level	 = iniparser_getint(ini, str, log_level);

            strcpy(str, g_log_app_name);
            strcat(str, ":mask");
            log_tag_mask[0] = iniparser_getint(ini, str, log_tag_mask[0]);
            for(i=1;i<LOG_MASK_SIZE;i++)
            {
                snprintf(str, sizeof(str), "%s:mask-%d", g_log_app_name, i);
                log_tag_mask[i] = iniparser_getint(ini, str, log_tag_mask[i]);
            }

            strcpy(str, g_log_app_name);
            strcat(str, ":log_max_mb");
            g_log_size_max = iniparser_getint(ini, str, g_log_size_max);

            strcpy(str, g_log_app_name);
            strcat(str, ":multi_closed_log");
            g_keep_multi_closed_log = iniparser_getint(ini, str, g_keep_multi_closed_log);

        }


        // When the disk size is below the threshold, the daemon may set
        // urgent log control and send signal 11 for reloading the vales
        log_level	 = iniparser_getint(ini, "urgent:level", log_level);
        log_tag_mask[0] = iniparser_getint(ini, "urgent:mask", log_tag_mask[0]);

        strncpy(g_log_mode, log, sizeof(g_log_mode)-1);

        iniparser_freedict(ini);
    }

    if(g_log_ctxs[DEFAULT_LOG_ID])
        g_log_ctxs[DEFAULT_LOG_ID]->max_size_KB = g_log_size_max * 1000;

    return ret;
}


void log_init(const char* app, const char* log_path, const char* log_cfg_path)
{
    char dir_path[512];
    // register signal handler anyway
    signal(SIGUSR1, signal_handler);
    signal(SIGUSR2, signal_handler);
    signal(SIGSEGV, signal_handler);
    catch_sigterm();

    if(g_log_queue == NULL)
    {
        g_log_queue = create_queue();
        if(g_log_queue )
        {
            pthread_t logger_tid = 0;
            if (pthread_create (&logger_tid, NULL, thread_logger, NULL))
            {
                printf( "can't create thread_logger :[%s]\n", strerror(errno));
            }
        }
    }

    g_log_dir = (char *) malloc(strlen(log_path) + 2);
    if(g_log_dir)
    {
        strcpy(g_log_dir, log_path);
        if(g_log_dir[strlen(g_log_dir)-1] != '/')
            strcat(g_log_dir, "/");
    }

    g_log_cfg_path = strdup(log_cfg_path);

    snprintf(dir_path, sizeof(dir_path),"%sclosed", g_log_dir);
    make_full_dir(dir_path);

    if(NULL == app)
    {
        printf("Log: app is set to app\n");
        app = "app";
    }

    strncpy(g_log_app_name, app, sizeof(g_log_app_name));

    log_update_config(1);
    const char* log_file_path = NULL;
    if(0 == strcmp("logfile", g_log_mode))
    {
        snprintf(dir_path, sizeof(dir_path), "%s%s.log", g_log_dir, g_log_app_name);
        log_file_path = dir_path;
    }
    log_init_slot(DEFAULT_LOG_ID, log_file_path, g_log_size_max * 1000);

    printf("print to %s, app_name=%s, log file: %s\n", log_to(), g_log_app_name, dir_path);
}

int log_read_level()
{
    return log_level;
}


void log_setlevel(int level)
{
    if((level >= LOG_VERBOSE) && (level <= LOG_ERROR))
    {
        log_level = level;
    }
}

void log_set_tag_mask(int tag_slot,int tag_mask)
{
    if(tag_slot < 0 || tag_slot >= LOG_MASK_SIZE)
        return;
    log_tag_mask[tag_slot] |= tag_mask;
}

void log_clear_tag_mask(int tag_slot, int tag_mask)
{
    if(tag_slot < 0 || tag_slot >= LOG_MASK_SIZE)
        return;
    log_tag_mask[tag_slot] &= ~tag_mask;
}

unsigned int g_log_drops = 0;
void do_log(log_id_t idx, const char * buf)
{
    if(idx >= MAX_LOG_CONTEXS)
        return;    
    if(buf == NULL) buf = "";
    if(g_log_queue == NULL)
    {
        fputs(buf, log_get_handle());
        fflush(log_get_handle());
    }
    else
    {
        int len = strlen(buf) + sizeof(log_body_t);
        log_body_t * payload = (log_body_t*) malloc(len);
        if(payload == NULL)
        {
            printf("%s: malloc %d bytes failed.\n", __FUNCTION__, len+1);
            return;
        }
        payload->idx = idx;
        strcpy(payload->log_content, buf);

        if(!post_msg_buffer (g_log_queue, MSG_TAG_NONE, (char **) &payload, len))
        {
            g_log_drops ++;
        }
    }
}

void log_print2(log_id_t slot, const char* tag, const char* fmt, va_list ap)
{
    struct tm tmp = {0};
    time_t t;

    char buf[LOG_BUF_SIZE];
    char * p = buf;
    int remain = sizeof(buf)-3;

    if(slot >= MAX_LOG_CONTEXS)
        return;

    // add a new line from the begin for the caller has such intent
    if(*fmt == '\n')
    {
        fmt ++;
        *p++ = '\n';
        remain --;
    }

    t = time(NULL);
    if(localtime_r(&t, &tmp))
    {
        int len = snprintf(p, remain, "%02d-%02d %02d:%02d:%02d [%s] ",
                (1+tmp.tm_mon), tmp.tm_mday,
                tmp.tm_hour, tmp.tm_min, tmp.tm_sec,
                tag?tag:"");
        p += len;
        remain -= len;
    }

    int cnt = vsnprintf(p, remain, fmt, ap);
    if(cnt > 0)
    {
        if(cnt > remain) 
            cnt = remain; 
        p += cnt;
        // append \r\n if not present
        if(*(p-1) != '\n')
        {
            *p++  = '\r';
            *p++ = '\n';
        }
        *p = '\0';
        do_log(slot,buf);
    }
}

void log_print(const char* tag, const char* fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    log_print2(DEFAULT_LOG_ID,tag,  fmt, ap);
    va_end(ap);
}


bool log_init_slot(log_id_t slot, const char * log_path, long max_size_KB)
{
    if(slot >= MAX_LOG_CONTEXS)
        return false;

    if(g_log_ctxs[slot] != NULL)
    {
        return false;
    }

    g_log_ctxs[slot] = (log_context_t*)malloc(sizeof(log_context_t));
    if(g_log_ctxs[slot] == NULL)
    {
        return false;
    }

    memset(g_log_ctxs[slot], 0 ,sizeof(log_context_t));
    if(log_path) {
        g_log_ctxs[slot]->log_fp = create_log(log_path);
        g_log_ctxs[slot]->log_path = strdup(log_path);
        if(g_log_ctxs[slot]->log_fp)
            fprintf(g_log_ctxs[slot]->log_fp,"Max log size: %ld KB\r\n", max_size_KB);
    }
    g_log_ctxs[slot]->max_size_KB = max_size_KB;
    return true;
}

void log_print_slot(log_id_t slot,const char* tag, const char* fmt, ...)
{
    if(slot >= MAX_LOG_CONTEXS) return;

    va_list ap;
    va_start(ap, fmt);
    log_print2(slot, tag, fmt, ap);
    va_end(ap);
}


void signal_handler(int signal)
{
    switch(signal)
    {
    case SIGUSR1:
        WARNING( "[%s] SIGUSR1 received, update config..", g_log_app_name);
        log_update_config(0);
        break;

    case SIGUSR2:
    {
        WARNING("[%s] SIGUSR2 received, generating new log..", g_log_app_name);
        void * body = NULL;
        msg_t msg = new_msg((void **)&body, CMD_REFRESH, NULL);
        post_msg(g_log_queue, msg);
        break;
    }
    case SIGSEGV:
        WARNING("[%s] SIGSEGV received, generating new log..", g_log_app_name);
        fprintf(log_get_handle(), "SIGSEGV received, exiting.. [%s] - line [%d]\n", __FUNCTION__, __LINE__);
        fflush(log_get_handle());
        exit(-1);
        break;
    }
}


void log_buffer(char * title, void * buffer,
        int length)
{

    FILE * stream = stderr;
    char buf[100];

    if (length == 0) return;

    if(get_log_fp(DEFAULT_LOG_ID)) stream = get_log_fp(DEFAULT_LOG_ID);
    fprintf(stream, "[%s] %s\n", now_str(buf), title?title:"data");

    bh_output_buffer(stream, buffer, length);
}

#ifdef LOG_TEST
int main(int argc, char** argv)
{
    signal(SIGUSR1, signal_handler);
    signal(SIGUSR2, signal_handler);

    ERROR("trace %d E", 1);
    //	ERROR("trace %d E", 2);

    while(1); // test signal, kill -10 or kill -12
    return 0;
}

#endif
